package com.tg.paw.common.generator;

import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;

/**
 * @author Rubble
 * Created on 2020/5/23
 */
@Slf4j
public class SnowflakeBeatHeart {

    public static final int NEXT_TIME = 30;

    public static final int RECONNECT_TIME = 3;

    private RedisTemplate redisTemplate;

    private SnowflakeProperties snowflakeProperties;

    private  int threadCount = 5;

    private ScheduledExecutorService executorService;

    private SnowflakeRegisterService snowflakeRegisterService;

    public SnowflakeBeatHeart(RedisTemplate redisTemplate, SnowflakeProperties snowflakeProperties, SnowflakeRegisterService snowflakeRegisterService){
        this.redisTemplate = redisTemplate;
        this.snowflakeProperties = snowflakeProperties;
        this.snowflakeRegisterService = snowflakeRegisterService;
        executorService = new ScheduledThreadPoolExecutor(threadCount, new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                thread.setDaemon(true);
                thread.setName("com.tg.paw.common.generator");
                return thread;
            }
        });
    }

    public void startSchedule(){
        executorService.schedule(new BeatTask(), 60, TimeUnit.SECONDS);
    }

    class BeatTask implements Runnable {

        public BeatTask(){
        }

        @Override
        public void run() {
            log.debug("SnowflakeBeatHeart run ");
            String key = snowflakeProperties.getSnowflakeKey();
            String uuid = snowflakeProperties.getUuid();

            /**
             *
             * 服务是指的 同一个服务 不同实例
             * 当前服务连接不到redis, 当重新链接时可能当前workerId已释放或被其他服务实例占用
             * 默认 心跳时间30s  过期时间90s 过期时间>2*心跳时间，断开链接redis中的workerId不会被其他服务实例占用
             * 断开链接： 重置uuid (使redis中的key-uuid 失效)， 重置snowflake(当前服务生成id服务不可用), 重新获取workId
             * 缩短重试时间, 当恢复链接 快速生成workerId
             *
             * redis 数据丢失  flushDB 后至少需等待一个心跳时间 才可以注册其他服务实例
             * 若没有等待一个心跳时间有其他服务实例注册，可能多个服务实例公用一个workerId，下一个心跳由于uuid不一样则自动重新注册
             */
            int nextTime = NEXT_TIME;
            try{
                String value = (String) redisTemplate.opsForValue().get(key);
                if(value!=null && value.equals(uuid)){
                    // 确认为当前持有的值 续期
                    redisTemplate.expire(snowflakeProperties.getSnowflakeKey(), SnowflakeRegisterService.EXPIRE_TIME, TimeUnit.SECONDS);
                }else{
                    // 重新生成 workerId
                    snowflakeRegisterService.registerWorker();
                }
            }catch (Exception ex){
                // redis 链接异常 清除当前workerId, ID生成器不可用, 重置uuid, 缩短重试时间 RECONNECT_TIME
                snowflakeRegisterService.resetWorker();
                nextTime = RECONNECT_TIME;
                log.error("SnowflakeBeatHeart error ", ex);
            }finally {
                // 保持心跳
                executorService.schedule(new BeatTask(), nextTime, TimeUnit.SECONDS);
            }



        }
    }
}
